/*
 *  Copyright 2010 UPF Telecos 4th group G.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  under the License.
 */

package edu.upf.network.impl;

import edu.upf.core.ClientLogic;
import edu.upf.core.User;
import edu.upf.filters.ChatFilter;
import edu.upf.filters.SearchResultFilter;
import edu.upf.filters.SearchProfileFilter;
import edu.upf.filters.UserContentFilter;
import edu.upf.utils.LogSystem;
import edu.upf.listeners.ChatListener;
import edu.upf.listeners.ContactListener;
import edu.upf.listeners.MediaTransferListener;
import edu.upf.listeners.SearchProfileListener;
import edu.upf.listeners.SearchResultListener;
import edu.upf.listeners.UserContentListener;
import edu.upf.messages.GenericMessage;
import edu.upf.network.ContentTransfer;
import edu.upf.network.InstantMessage;
import edu.upf.utils.Definitions;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.RosterEntry;
import org.jivesoftware.smack.SASLAuthentication;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smackx.filetransfer.FileTransferManager;
import org.jivesoftware.smackx.filetransfer.OutgoingFileTransfer;

/**
 * Class that provides all the XMPP functionalities using the smack library
 * @author asustatil
 */
public class XMPPClient extends Thread implements Definitions, InstantMessage {

    private XMPPConnection con = null;
    private String loginName = "";
    private String pass="";
    private String resource="";
    private FileTransferManager manager;
    private ClientLogic clientLogic;
    private Roster roster;
    private LogSystem log;
    private boolean loginTask;
    private Collection packetListeners;
    private Map<String,MediaTransferListener> transferListeners;
    private ContactListener contactListener;

    /**
     * Constructor, initializes variables needed for a correct functioning and
     * receives a object of the client logic for callbacks
     * @param clientLogic
     */
    public XMPPClient(ClientLogic clientLogic){
        this.clientLogic=clientLogic;
        log=new LogSystem(XMPPClient.class);
        //listeners=new HashMap();
        ConnectionConfiguration config = new ConnectionConfiguration(
                GTALK_SERVER, GENERIC_PORT, PROXY);
        config.setSASLAuthenticationEnabled(true);
        config.setReconnectionAllowed(true);
        con = new XMPPConnection(config);
        packetListeners = new ArrayList();
        transferListeners = new HashMap();
    }

    @Override
    public void run(){
        if (loginTask) {
            loginTask();
        }
    }

    @Override
    public boolean logout() {
        boolean status=false;
        if (con != null) {
            con.disconnect();
            status = true;
        }
        removeListeners();
        clientLogic.updateState(LOGOUT_STATE);
        log.addBoolDebug("Logout: ",status);
        return status;
    }

    @Override
    public boolean login(String name, final String pass, final String resource){
        loginName=name;
        this.pass=pass;
        this.resource=resource;
        loginTask=true;
        this.start();
        return true;
    }

    public void loginTask(){
        
        clientLogic.updateState(LOGGING_STATE);
        try {
            con.connect();
            SASLAuthentication.supportSASLMechanism(PLAIN_AUTH, PLAIN_MECHANISM);
            con.login(loginName, pass, resource);
            roster=con.getRoster();

            initListeners();
            initFriendList();
            // Create the file transfer manager
            manager = new FileTransferManager(con);

            clientLogic.updateState(LOGGED_STATE);

        } catch (XMPPException ex) {
            clientLogic.showExceptionMessage(ex,INFO);
            removeListeners();
            clientLogic.updateState(LOGOUT_STATE);
        }
        log.addDebugLog("Login succesful");
    }


    public void stopLoginTask(){
        this.interrupt();
        clientLogic.updateState(LOGOUT_STATE);
    }

    @Override
    public void sendChatMessage(String dest, String text){
        Message msg = new Message(dest, Message.Type.chat);
        msg.setBody(text);
        con.sendPacket(msg);
    }

    @Override
    public void sendGenericMessage(GenericMessage gmsg){
        Message msg = gmsg.getMessage();
        log.addDebugLog("Sending message to:" + msg.getTo());
        con.sendPacket(msg);
    }

    @Override
    public void sendMedia(File media, String dest, String descr) {

        OutgoingFileTransfer outTransfer = manager.createOutgoingFileTransfer(dest);

        ContentTransfer contentTransfer = new OutgoingContentTransfer(outTransfer);
        clientLogic.addContentTransfer(contentTransfer);
        try {
            outTransfer.sendFile(media, descr);
        } catch (XMPPException ex) {
            clientLogic.showExceptionMessage(ex,WARNING);
        }
    }

    private void initListeners() {
        log.addDebugLog("Initing Listeners...");

        contactListener = new ContactListener(this);
        roster.addRosterListener(contactListener);

        PacketListener packetListener = new ChatListener(clientLogic);
        packetListeners.add(packetListener);
        con.addPacketListener(packetListener, new ChatFilter());

        packetListener = new SearchProfileListener(clientLogic);
        packetListeners.add(packetListener);
        con.addPacketListener(packetListener , new SearchProfileFilter());
        
        packetListener = new UserContentListener(clientLogic);
        packetListeners.add(packetListener);
        con.addPacketListener(packetListener, new UserContentFilter());

        log.addDebugLog("Listeners added");
    }

    private void removeListeners(){

        Iterator<PacketListener> currentListener=packetListeners.iterator();
        while(currentListener.hasNext()){
            con.removePacketListener(currentListener.next());
        }
        packetListeners.clear();

        Iterator<MediaTransferListener> mediaListener=transferListeners.values().iterator();
        while(mediaListener.hasNext()){
            manager.removeFileTransferListener(mediaListener.next());
        }
        transferListeners.clear();

        roster.removeRosterListener(contactListener);
    }

    /**
     * Removes all the previous result listeners, useful when a new search
     * is submitted.
     */
    public void removeResultListeners(){
        Iterator<PacketListener> currentListener=packetListeners.iterator();
        Collection searchListeners = new ArrayList();
        while(currentListener.hasNext()){
            PacketListener packetListener = currentListener.next();
            if(packetListener instanceof SearchResultListener){
                con.removePacketListener((PacketListener)packetListener);
                searchListeners.add(packetListener);
            }
        }
        packetListeners.removeAll(searchListeners);
    }
    
    private void initFriendList(){
        // get all user from your friends list
        for (Iterator<RosterEntry> i = roster.getEntries().iterator(); i.hasNext();) {
            RosterEntry re = (RosterEntry) i.next();
            User user = new User(getNameShown(re),re.getUser());
            clientLogic.addBuddyUser(user);
        }
    }

    /**
     * Method that updates the presence of a collection of roster entries
     * @param arg0
     */
    public void updateEntries(Collection<String> arg0){
        Iterator<String> li = arg0.iterator();

        // Display the next elements:
        while (li.hasNext()) {
            Presence presence = roster.getPresence(li.next());
            updatePresence(presence);
        }
    }
    
    /**
     * Method that updates the presence of given user, receives a Presence
     * object provided in the smack library with all the necesary information
     * @param presence
     */
    public void updatePresence(Presence presence){
        String address = filterAddressResource(presence.getFrom());
        String name = getNameShown(roster.getEntry(address));
        User user = new User(name,address);
        user.setAvailable(presence.getType() == Presence.Type.available);
        clientLogic.updateUserPresence(user);
    }

    /**
     * Adds a search result listener to the connection
     * @param query
     */
    public void addSearchResultListener(String query){
        PacketListener resultListener = new SearchResultListener(clientLogic);
        packetListeners.add(resultListener);
        con.addPacketListener(resultListener, new SearchResultFilter(query));
    }

    /**
     * Adds a tranfer listener to the connection
     * @param transferID
     * @param viewMode 
     */
    public void addTransferListener(String transferID, boolean viewMode){
        MediaTransferListener transferListener =
                new MediaTransferListener(clientLogic,transferID,viewMode);
        transferListeners.put(transferID,transferListener);
        manager.addFileTransferListener(transferListener);
    }

    /**
     * Adds a tranfer listener to the connection
     * @param transferID
     */
    public void removeTransferListener(String transferID){    
        MediaTransferListener listener = transferListeners.remove(transferID);
        manager.removeFileTransferListener(listener);
    }

        /**
     * Returns the contact direction of a user eliminating the resource
     * @param direc
     * @return
     */
    public String filterAddressResource(String direc) {
        if (direc.matches(".*/.*")) {
            return direc.split("/", 2)[0];
        } else {
            return direc;
        }
    }

    private String getNameShown(RosterEntry re) {
        String name = re.getName();
        if (name == null) {
            return re.getUser();
        }
        return name;
    }
}
